package server

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"ngrok/msg"
	"os"
	"os/exec"
	"sort"
	"strconv"
	"strings"
	"time"
)

var rootdir string
var hostip string

func formatTimeHours(a time.Time, b time.Time) (str string) {
	str = "未知"
	if a.After(b) {
		diff := a.Unix() - b.Unix()
		if 60 > diff {
			str = "1分钟"
		} else if 3600 > diff {
			str = fmt.Sprintf("%d分钟", diff/60)
		} else {
			str = fmt.Sprintf("%d小时%d分钟", diff/3600, diff%3600/60)
		}
	}
	return
}

func httpSetHander(w http.ResponseWriter, content_type string) {
	w.Header().Set("Server", "CGI")
	w.Header().Set("Connection", "close")
	w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0")
	w.Header().Set("Pragma", "no-cache")
	w.Header().Set("Expires", "Mon, 1 Jun 1970 08:00:00 GMT")
	w.Header().Set("Content-Type", content_type)
}

func showForbidden(w http.ResponseWriter) {
	httpSetHander(w, "text/html; charset=utf-8")

	w.WriteHeader(http.StatusForbidden)

	fmt.Fprintf(w, "<!DOCTYPE HTML>")
	fmt.Fprintf(w, "<html>")
	fmt.Fprintf(w, "<head>")
	fmt.Fprintf(w, "<title>错误</title>")
	fmt.Fprintf(w, "</style>")
	fmt.Fprintf(w, "</head>")
	fmt.Fprintf(w, "<body>")
	fmt.Fprintf(w, "<h2>未经授权的访问</h2>")
	fmt.Fprintf(w, "</body>")
	fmt.Fprintf(w, "</html>")
}

func showNotFound(w http.ResponseWriter) {
	httpSetHander(w, "text/html; charset=utf-8")

	w.WriteHeader(http.StatusNotFound)

	fmt.Fprintf(w, "<!DOCTYPE HTML>")
	fmt.Fprintf(w, "<html>")
	fmt.Fprintf(w, "<head>")
	fmt.Fprintf(w, "<title>错误</title>")
	fmt.Fprintf(w, "</style>")
	fmt.Fprintf(w, "</head>")
	fmt.Fprintf(w, "<body>")
	fmt.Fprintf(w, "<h2>该页不存在</h2>")
	fmt.Fprintf(w, "<h3>请检查您的url地址是否正确</h3>")
	fmt.Fprintf(w, "</body>")
	fmt.Fprintf(w, "</html>")
}

func showInvalidArgument(w http.ResponseWriter) {
	httpSetHander(w, "text/html; charset=utf-8")

	w.WriteHeader(http.StatusForbidden)

	fmt.Fprintf(w, "<!DOCTYPE HTML>")
	fmt.Fprintf(w, "<html>")
	fmt.Fprintf(w, "<head>")
	fmt.Fprintf(w, "<title>错误</title>")
	fmt.Fprintf(w, "</style>")
	fmt.Fprintf(w, "</head>")
	fmt.Fprintf(w, "<body>")
	fmt.Fprintf(w, "<h2>参数非法，请检查</h2>")
	fmt.Fprintf(w, "</body>")
	fmt.Fprintf(w, "</html>")
}

func showConfig(w http.ResponseWriter) {
	filepath := rootdir + "/ngrok.cfg"
	if fl, err := os.Stat(filepath); nil == err {
		if !fl.IsDir() && 102400 > fl.Size() {
			if f, err := os.Open(filepath); nil == err {
				httpSetHander(w, "text/plain; charset=utf-8")

				w.Header().Set("Content-Length", strconv.FormatInt(fl.Size(), 10))

				w.WriteHeader(http.StatusOK)

				io.Copy(w, f)
			}
		}
	}
}

func showClient(w http.ResponseWriter, cid string) {
	httpSetHander(w, "text/html; charset=utf-8")

	w.WriteHeader(http.StatusOK)

	fmt.Fprintf(w, "<!DOCTYPE HTML>")
	fmt.Fprintf(w, "<html>")
	fmt.Fprintf(w, "<head>")
	fmt.Fprintf(w, "<title>客户端列表</title>")
	fmt.Fprintf(w, "<style type=\"text/css\">")
	fmt.Fprintf(w, "table, td, th { padding: 10px; border-collapse: collapse; border-style: groove; border-width: 1px; }")
	fmt.Fprintf(w, "td.left { text-align: left; }")
	fmt.Fprintf(w, "td.center { text-align: center; }")
	fmt.Fprintf(w, "td.right { text-align: right; }")
	fmt.Fprintf(w, "</style>")
	fmt.Fprintf(w, "</head>")
	fmt.Fprintf(w, "<body>")

	var dev_found bool = false

	controlRegistry.RLock()

	for _, ctl := range controlRegistry.controls {
		if cid == ctl.id {
			dev_found = true

			fmt.Fprintf(w, "<div>")
			fmt.Fprintf(w, "<table style=\"width: 800px\">")
			fmt.Fprintf(w, "<tbody>")
			fmt.Fprintf(w, "<tr><th style=\"width: 100px\">参数名</th><th>参数值</th></tr>")
			fmt.Fprintf(w, "<tr><td>设备ID</td><td>%s</td></tr>", ctl.id)
			fmt.Fprintf(w, "<tr><td>设备名称</td><td>%s</td></tr>", ctl.comment)
			if "UNKOWN" != ctl.updatedomain {
				fmt.Fprintf(w, "<tr><td>配置方法</td><td><a href=\"%s&mode=config\" target=\"_blank\">%s&mode=config</a></td></tr>", ctl.updatedomain, ctl.updatedomain)
			} else {
				fmt.Fprintf(w, "<tr><td>配置方法</td><td>%s</td></tr>", ctl.updatedomain)
			}
			for i, ip := range strings.Split(ctl.localaddr, "+") {
				fmt.Fprintf(w, "<tr><td>设备IP[%d]</td><td>%s</td></tr>", i+1, ip)
			}
			fmt.Fprintf(w, "<tr>")
			fmt.Fprintf(w, "<td>管理</td>")
			fmt.Fprintf(w, "<td>")
			fmt.Fprintf(w, "<a href=\"/client?cid=%s&cmd=disconnect\">中断</a> | ", ctl.id)
			fmt.Fprintf(w, "<a href=\"/client?cid=%s&cmd=exit\">退出</a> | ", ctl.id)
			fmt.Fprintf(w, "<a href=\"/client?cid=%s&cmd=reboot\">重启</a>", ctl.id)
			fmt.Fprintf(w, "</td>")
			fmt.Fprintf(w, "</tr>")
			fmt.Fprintf(w, "</tbody>")
			fmt.Fprintf(w, "</table>")
			fmt.Fprintf(w, "</div>")

			fmt.Fprintf(w, "<div>")
			fmt.Fprintf(w, "<table style=\"width: 800px\">")
			fmt.Fprintf(w, "<tr><th style=\"width: 100px\">名称</th><th>公网URL</th><th>转发值</th><th>动态</th></tr>")
			for _, t := range ctl.tunnels {
				http_url := t.url
				if parsedUrl, err := url.Parse(http_url); err == nil {
					http_url = hostip + ":" + parsedUrl.Port()
				}
				fmt.Fprintf(w, "<tr><td>%s</td><td>%s</td><td>%s</td><td><a href=\"http://%s\" target=\"_blank\">HTTP访问</a></td></tr>", t.subdomain, http_url, t.target, http_url)
			}
			fmt.Fprintf(w, "</table>")
			fmt.Fprintf(w, "</div>")
		}
	}

	controlRegistry.RUnlock()

	if !dev_found {
		fmt.Fprintf(w, "<div><span>无法找到此设备，请<a href=\"/\">返回</a></span></div>")
	}

	fmt.Fprintf(w, "</body>")
	fmt.Fprintf(w, "</html>")
}

func showClientCmd(w http.ResponseWriter, cid string, cmd string) {
	httpSetHander(w, "text/html; charset=utf-8")

	w.WriteHeader(http.StatusOK)

	var op_ok bool = false
	var cmd_id int = 0

	switch cmd {
	case "disconnect":
		cmd_id = 1
	case "exit":
		cmd_id = 2
	case "reboot":
		cmd_id = 3
	default:
	}

	if 0 < cmd_id {
		controlRegistry.RLock()

		for _, ctl := range controlRegistry.controls {
			if cid == ctl.id {
				switch cmd_id {
				case 1:
					ctl.shutdown.Begin()
					op_ok = true

				case 2:
					ctl.out <- &msg.Exit{}
					op_ok = true

				case 3:
					ctl.out <- &msg.Reboot{}
					op_ok = true

				default:
				}
			}
		}

		controlRegistry.RUnlock()
	}

	fmt.Fprintf(w, "<!DOCTYPE HTML>")
	fmt.Fprintf(w, "<html>")
	fmt.Fprintf(w, "<head>")
	fmt.Fprintf(w, "<title>客户端操作</title>")
	fmt.Fprintf(w, "<meta http-equiv=\"refresh\" content=\"0.1; url=/\">")
	fmt.Fprintf(w, "</head>")
	fmt.Fprintf(w, "<body>")

	fmt.Fprintf(w, "<script>")

	if !op_ok {
		fmt.Fprintf(w, "alert(\"操作失败\");")
	} else {
		fmt.Fprintf(w, "alert(\"操作成功\");")
	}

	fmt.Fprintf(w, "window.location.href='/';")

	fmt.Fprintf(w, "</script>")

	if !op_ok {
		fmt.Fprintf(w, "<div><span>执行失败，请<a href=\"/\">返回</a></span></div>")
	} else {
		fmt.Fprintf(w, "<div><span>执行成功，请<a href=\"/\">返回</a></span></div>")
	}

	fmt.Fprintf(w, "</body>")
	fmt.Fprintf(w, "</html>")
}

func showStatic(w http.ResponseWriter) {
	httpSetHander(w, "text/html; charset=utf-8")

	w.WriteHeader(http.StatusOK)

	fmt.Fprintf(w, "<!DOCTYPE HTML>")
	fmt.Fprintf(w, "<html>")
	fmt.Fprintf(w, "<head>")
	fmt.Fprintf(w, "<title>客户端列表</title>")
	fmt.Fprintf(w, "<style type=\"text/css\">")
	fmt.Fprintf(w, "table, td, th { padding: 10px; border-collapse: collapse; border-style: groove; border-width: 1px; }")
	fmt.Fprintf(w, "td.left { text-align: left; }")
	fmt.Fprintf(w, "td.center { text-align: center; }")
	fmt.Fprintf(w, "td.right { text-align: right; }")
	fmt.Fprintf(w, "</style>")
	fmt.Fprintf(w, "</head>")
	fmt.Fprintf(w, "<body>")

	fmt.Fprintf(w, "<div>")

	fmt.Fprintf(w, "<table style=\"width: 1024px\">")

	fmt.Fprintf(w, "<tbody>")

	fmt.Fprintf(w, "<tr>")
	fmt.Fprintf(w, "<th style=\"width: 27%%\">设备ID</th>")
	fmt.Fprintf(w, "<th style=\"width: 8%%\">版本</th>")
	fmt.Fprintf(w, "<th style=\"width: 13%%\">在线时长</th>")
	fmt.Fprintf(w, "<th style=\"width: 13%%\">设备IP</th>")
	fmt.Fprintf(w, "<th>设备名称</th>")
	fmt.Fprintf(w, "</tr>")

	var key_table []string
	var nowTime = time.Now()

	controlRegistry.RLock()

	for _, ctl := range controlRegistry.controls {
		var buffer bytes.Buffer

		buffer.WriteString("<tr>")
		buffer.WriteString(fmt.Sprintf("<td class=\"right\"><a href=\"/client?cid=%s\">%s</a></td>", ctl.id, ctl.id))
		buffer.WriteString(fmt.Sprintf("<td class=\"center\">%s</td>", ctl.hashver))
		buffer.WriteString(fmt.Sprintf("<td class=\"center\">%s</td>", formatTimeHours(nowTime, ctl.connTime)))

		buffer.WriteString("<td class=\"center\">")

		for i, ip := range strings.Split(ctl.localaddr, "+") {
			if 0 != i {
				buffer.WriteString("<br />")
			}
			buffer.WriteString(fmt.Sprintf("%s", ip))
		}

		buffer.WriteString("</td>")

		buffer.WriteString(fmt.Sprintf("<td class=\"left\">%s</td>", ctl.comment))

		buffer.WriteString("</tr>")

		key_table = append(key_table, buffer.String())
	}

	controlRegistry.RUnlock()

	sort.Strings(key_table)

	fmt.Fprintf(w, strings.Join(key_table, ""))

	fmt.Fprintf(w, "</tbody>")

	fmt.Fprintf(w, "</table>")

	fmt.Fprintf(w, "</div>")

	fmt.Fprintf(w, "</body>")
	fmt.Fprintf(w, "</html>")
}

func showRedirect(w http.ResponseWriter) {
	w.Header().Set("Location", "/index")

	w.WriteHeader(http.StatusMovedPermanently)
}

func checkAuthorization(w http.ResponseWriter, r *http.Request) (ret bool) {
	auth := r.Header.Get("Authorization")
	if auth != "" {
		auths := strings.SplitN(auth, " ", 2)
		if len(auths) == 2 {
			authMethod := auths[0]
			authB64 := auths[1]
			switch authMethod {
			case "Basic":
				authstr, err := base64.StdEncoding.DecodeString(authB64)
				if err == nil {
					userPwd := strings.SplitN(string(authstr), ":", 2)
					if len(userPwd) == 2 {
						if 0 < len(userPwd[0]) && 0 < len(userPwd[1]) {
							bts, err := ioutil.ReadFile(rootdir + "/ngrok_auth.json")
							if nil == err {
								var maps map[string]interface{}
								err = json.Unmarshal(bts, &maps)
								if nil == err {
									if u := maps["username"].(string); 0 < len(u) {
										if p := maps["password"].(string); 0 < len(p) {
											if userPwd[0] == u && userPwd[1] == p {
												ret = true
											}
										}
									}
								}
							}

						}
					}
				}
			}
		}
	}

	if !ret {
		httpSetHander(w, "text/html; charset=utf-8")

		w.Header().Set("WWW-Authenticate", "Basic realm=\"Need authorization\"")

		w.WriteHeader(http.StatusUnauthorized)

		fmt.Fprintf(w, "<!DOCTYPE HTML>")
		fmt.Fprintf(w, "<html>")
		fmt.Fprintf(w, "<head>")
		fmt.Fprintf(w, "<title>错误</title>")
		fmt.Fprintf(w, "</style>")
		fmt.Fprintf(w, "</head>")
		fmt.Fprintf(w, "<body>")
		fmt.Fprintf(w, "<h2>警告：非法访问，请刷新页面验证权限</h2>")
		fmt.Fprintf(w, "</body>")
		fmt.Fprintf(w, "</html>")
	}

	return
}

func getHostIP(r *http.Request) (ip string) {
	scheme := "http://"
	if r.TLS != nil {
		scheme = "https://"
	}

	if parsedUrl, err := url.Parse(strings.Join([]string{scheme, r.Host, r.RequestURI}, "")); err == nil {
		ns, err := net.LookupHost(parsedUrl.Hostname())
		if err == nil {
			for _, n := range ns {
				ip = n
				break
			}
		}
	}

	return
}

func mainHandler(w http.ResponseWriter, r *http.Request) {
	if 0 == len(hostip) {
		hostip = getHostIP(r)
	}

	if http.MethodGet == r.Method {
		if r.URL != nil {
			var url = *r.URL
			switch url.Path {
			case "/":
				showRedirect(w)

			case "/index":
				showStatic(w)

			case "/client":
				if ok := checkAuthorization(w, r); ok {
					if !url.ForceQuery && "" != url.RawQuery {
						if cid := r.FormValue("cid"); 0 < len(cid) {
							if cmd := r.FormValue("cmd"); 0 < len(cmd) {
								showClientCmd(w, cid, cmd)
							} else {
								showClient(w, cid)
							}
						} else {
							showInvalidArgument(w)
						}
					} else {
						showRedirect(w)
					}
				}

			case "/system":
				if !url.ForceQuery && "" != url.RawQuery {
					if cmd := r.FormValue("cmd"); 0 < len(cmd) {
						switch cmd {
						case "config":
							showConfig(w)

						case "reboot":
							if ok := checkAuthorization(w, r); ok {
								showRedirect(w)

								shell := exec.Command("sh", "-c", "\"reboot\"", "&")
								if nil != shell {
									shell.Start()
								}
							}

						default:
							showInvalidArgument(w)
						}
					}
				} else {
					showRedirect(w)
				}

			default:
				showNotFound(w)
			}
		} else {
			showStatic(w)
		}
	} else if http.MethodPost == r.Method {
		fmt.Fprintf(w, "{\"status\": false}")
	} else {
		showForbidden(w)
	}
}

// Listens for new manage listener from the public internet
func startManageListener(addr string, root string) {
	http.HandleFunc("/", mainHandler)
	rootdir = root
	go http.ListenAndServe(addr, nil)
}
